package com.zxx.myclock.widget

import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View
import android.view.animation.LinearInterpolator
import java.util.*
import kotlin.properties.Delegates

/**
 * https://juejin.im/post/5cb53e93e51d456e55623b07
 * 文字时钟
 * @author zxx on 2019/7/24
 */
class TextClockView @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    //全部画笔
    private val mPaint = createPaint()
    private val mHelperPaint = createPaint(color = Color.RED)
    //这里定义了一个字段 并告诉系统 该对象不为null，目的是为了在以后不用写 mWidth?. 并且同时相当于懒加载
//    private var mWidth: Float by Delegates.notNull()    //委托属性
    private var mWidth: Float = -1f    //委托属性
    //https://blog.csdn.net/qq285016127/article/details/72788690
    private var mHeight: Float by Delegates.notNull()

    private var mHourR: Float by Delegates.notNull()
    private var mMinuteR: Float by Delegates.notNull()
    private var mSecondR: Float by Delegates.notNull()

    //三个角度的全局变量
    private var mHourDeg: Float by Delegates.notNull()
    private var mMinuteDeg: Float by Delegates.notNull()
    private var mSecondDeg: Float by Delegates.notNull()

    private var spacing = 8f
    private var mAnimator: ValueAnimator by Delegates.notNull()

    init {
        //处理动画，声明全局的处理器
        mAnimator = ValueAnimator.ofFloat(6f, 0f)//由6降到1
        mAnimator.duration = 150
        mAnimator.interpolator = LinearInterpolator()   //插值器设为线性
        doInvalidate()
    }

    //在onLayout方法中计算View去除padding后的宽高
    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
        mWidth = (measuredWidth - paddingLeft - paddingRight).toFloat()
        mHeight = (measuredHeight - paddingTop - paddingBottom).toFloat()

        //统一用View宽度*系数来处理大小，这样可以联动适配样式
        mHourR = mWidth * 0.143f
        mMinuteR = mWidth * 0.35f
        mSecondR = mWidth * 0.35f + spacing //+spacing是为了有些距离（没有其他含义）
    }

    //在onDraw方法将画布原点平移到中心位置
    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        if (canvas == null) return

        //设置画布颜色
        //1.设置单一颜色为Canvas的背景颜色。
        //drawColor(int color)
        //2.使用指定的颜色和模式填充Canvas
        //drawColor(int color, PorterDuff.Mode mode)

        /*
        *  a 在画布上绘制的透明度,取值范围(0..255).
        *  r 在画布上绘制的红色色值,取值范围(0..255).
        *  g 在画布上绘制的绿色色值,取值范围(0..255).
        *  b 在画布上绘制的蓝色色值,取值范围(0..255).
        * 使用指定的ARGB填充Canvas。
        */
        //drawARGB(int a, int r, int g, int b)

        /*
        *  r 在画布上绘制的红色色值,取值范围(0..255).
        *  g 在画布上绘制的绿色色值,取值范围(0..255).
        *  b 在画布上绘制的蓝色色值,取值范围(0..255).
        * 使用指定的RGB颜色，填充Canvas。
        */
        //drawRGB (int r, int g, int b)

        canvas.drawColor(Color.BLACK)
        canvas.save()
        canvas.translate(mWidth / 2, mHeight / 2)  //原点移动到中心

        //绘制各元件
        drawCenterInfo(canvas)
        drawHour(canvas, mHourDeg)
        drawMinute(canvas, mMinuteDeg)
        drawSecond(canvas, mSecondDeg)

        //从原点处向右画一条辅助线，之后要处理文字与x轴的对齐问题
        //canvas.drawLine(0f, 0f, mWidth, 0f, mHelperPaint)

        canvas.restore()
    }


    /**
     * 开始绘制
     */
    //一开始没有参数，这里是为了供动态壁纸使用
    fun doInvalidate(block: (() -> Unit)? = null) {
        //供动态壁纸使用
        this.mBlock = block

        Calendar.getInstance().run {
            val hour = get(Calendar.HOUR)
            val minute = get(Calendar.MINUTE)
            val second = get(Calendar.SECOND)

            //这里将三个角度与实际时间关联起来，当前几点几分几秒，就把相应的圈逆时针旋转多少
            mHourDeg = -360 / 12f * (hour - 1)
            mMinuteDeg = -360 / 60f * (minute - 1)
            mSecondDeg = -360 / 60f * (second - 1)

//            invalidate()

            //为了转的优雅怎利用线性线性旋转的效果。所以添加以下动画
            //记录当前角度，然后让秒圈线性的旋转6°
            val hd = mHourDeg
            val md = mMinuteDeg
            val sd = mSecondDeg

            //处理动画
            mAnimator.removeAllUpdateListeners()//需要移除先前的监听
            mAnimator.addUpdateListener {
                val av = it.animatedValue as Float

                if (minute == 0 && second == 0) {
                    mHourDeg = hd + av * 5  //时圈旋转角度是分秒的5倍，线性的旋转30°
                }
                if (second == 0) {
                    mMinuteDeg = md + av  //线性的旋转6°
                }
                mSecondDeg = sd + av  //线性的旋转6°

                //invalidate()

                //供动态壁纸使用
                if (this@TextClockView.mBlock != null) {
                    this@TextClockView.mBlock?.invoke()
                } else {
                    invalidate()
                }
            }
            mAnimator.start()
        }
    }

    //绘制圆中信息
    private fun drawCenterInfo(canvas: Canvas) {
        Calendar.getInstance().run {
            //绘制数字时间
            val hour = get(Calendar.HOUR_OF_DAY)
            var minute = get(Calendar.MINUTE)
            val minuteStr = if (minute < 10) "0$minute" else "$minute"

            //字体大小根据「时圈」半径来计算
            mPaint.textSize = mHourR * 0.4f
            mPaint.alpha = 255
            mPaint.textAlign = Paint.Align.CENTER

            //canvas.drawText()方法的第三个参数是y坐标，但这个指的是文字的Baseline的y坐标,所以写了工具方法来得到矫正后的y坐标。
            canvas.drawText("$hour:$minuteStr", 0f, mPaint.getBottomedY(), mPaint)

            //绘制月份、星期
            val month = (this.get(Calendar.MONTH) + 1).let {
                if (it < 10) "0$it" else "$it"
            }
            val day = this.get(Calendar.DAY_OF_MONTH)
            //私有的扩展方法，将Int数字转换为 一、十一、二十等，后文绘制三个文字圈都会用该方法
            val dayOfWeek = (get(Calendar.DAY_OF_WEEK) - 1).toText()

            //字体大小根据「时圈」半径来计算
            mPaint.textSize = mHourR * 0.16f
            mPaint.alpha = 255
            mPaint.textAlign = Paint.Align.CENTER
            canvas.drawText("$month.$day 星期$dayOfWeek", 0f, mPaint.getTopedY(), mPaint)
        }
    }

    //绘制小时
    private fun drawHour(canvas: Canvas, degrees: Float) {
        mPaint.textSize = mHourR * 0.16f

        //处理整体旋转
        canvas.save()
        canvas.rotate(degrees)

        //for循环12次(0-11)，每次将画布旋转30°乘以i
        for (i in 0 until 12) {
            canvas.save()
            val iDeg = 360 / 12f * i
            canvas.rotate(iDeg)

            //这里处理当前时间点的透明度，因为degrees控制整体逆时针旋转
            //iDeg控制绘制时顺时针，所以两者和为0时，刚好在x正半轴上，也就是起始绘制位置。
            mPaint.alpha = if (iDeg + degrees == 0f) 255 else (0.6f * 255).toInt()
            mPaint.textAlign = Paint.Align.LEFT

            canvas.drawText("${(i + 1).toText()}点", mHourR, mPaint.getCenteredY(), mPaint)
            canvas.restore()
        }

        canvas.restore()
    }

    //绘制分钟
    private fun drawMinute(canvas: Canvas, degrees: Float) {
        mPaint.textSize = mHourR * 0.16f

        //处理整体旋转
        canvas.save()
        canvas.rotate(degrees)

        for (i in 0 until 60) {
            canvas.save()

            val iDeg = 360 / 60f * i
            canvas.rotate(iDeg)

            mPaint.alpha = if (iDeg + degrees == 0f) 255 else (0.6f * 255).toInt()
            mPaint.textAlign = Paint.Align.RIGHT

            if (i < 59) {
                canvas.drawText("${(i + 1).toText()}分", mMinuteR, mPaint.getCenteredY(), mPaint)
            }
            canvas.restore()
        }

        canvas.restore()
    }

    //绘制秒
    private fun drawSecond(canvas: Canvas, degrees: Float) {
        mPaint.textSize = mHourR * 0.16f

        //处理整体旋转
        canvas.save()
        canvas.rotate(degrees)

        //[1,60)
        for (i in 0 until 60) {
            canvas.save()

            val iDeg = 360 / 60f * i
            canvas.rotate(iDeg)

            mPaint.alpha = if (iDeg + degrees == 0f) 255 else (0.6f * 255).toInt()
            mPaint.textAlign = Paint.Align.LEFT

            //以内圈画秒字
//            if (i < 59) {
//                canvas.drawText("${(i + 1).toText()}秒", mSecondR, mPaint.getCenteredY(), mPaint)
//            }

            //以外圈对齐
            if (i < 10) {
                canvas.drawText("${(i + 1).toText()}秒", mSecondR + 2 * getFontWidth(mPaint, "十"),
                        mPaint.getCenteredY(), mPaint)
            } else if (i in 10..19 || i == 29 || i == 39 || i == 49) {
                //[10,19]
                canvas.drawText("${(i + 1).toText()}秒", mSecondR + getFontWidth(mPaint, "十"),
                        mPaint.getCenteredY(), mPaint)
            } else if (i < 59) {
                canvas.drawText("${(i + 1).toText()}秒", mSecondR, mPaint.getCenteredY(), mPaint)
            }

            canvas.restore()
        }

        canvas.restore()
    }

    /**
     * 创建画笔
     */
    private fun createPaint(colorString: String? = null, color: Int = Color.WHITE): Paint {
        return Paint().apply {
            this.color = if (colorString != null) Color.parseColor(colorString) else color
            this.isAntiAlias = true
            this.style = Paint.Style.FILL
        }
    }

    //    -----扩展方法---
    /**
     * 扩展获取绘制文字时在x轴上 贴紧x轴的上边缘的y坐标
     */
    private fun Paint.getBottomedY(): Float {
        return -this.fontMetrics.bottom
    }

    /**
     * 扩展获取绘制文字时在x轴上 贴近x轴的下边缘的y坐标
     */
    private fun Paint.getTopedY(): Float {
        return -this.fontMetrics.ascent
    }

    /**
     * 扩展获取绘制文字时在x轴上 垂直居中的y坐标
     */
    private fun Paint.getCenteredY(): Float {
        return this.fontSpacing / 2 - this.fontMetrics.bottom
    }

    /**
     *
     */
    private fun Int.toText(): String {
        var result = ""
        val iArr = "$this".toCharArray().map { it.toString().toInt() }

        //处理 10，11，12.. 20，21，22.. 等情况
        if (iArr.size > 1) {
            if (iArr[0] != 1) {
                result += NUMBER_TEXT_LIST[iArr[0]]
            }
            result += "十"
            if (iArr[1] > 0) {
                result += NUMBER_TEXT_LIST[iArr[1]]
            }
        } else {
            result = NUMBER_TEXT_LIST[iArr[0]]
        }
        return result
    }

    companion object {
        private val NUMBER_TEXT_LIST = listOf(
                "日", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十"
        )
    }

    private fun dp2px(value: Float): Float {
        val scale = resources.displayMetrics.density
        return value * scale + 0.5f
    }

    //获取文字的宽度
    private fun getFontWidth(paint: Paint, text: String): Float {
        return paint.measureText(text)
    }

    //返回指定的文字高度
    private fun getFontHeight(paint: Paint): Float {
        val fm: Paint.FontMetrics = paint.fontMetrics  //getFontMetrics();
        return fm.descent - fm.ascent
    }


    /*------这部分是动态壁纸--------------*/
    //绘制方法的回调，供动态壁纸使用
    private var mBlock: (() -> Unit)? = null

    //初始化宽高，供动态壁纸使用
    fun initWidthHeight(width: Float, height: Float) {
        if (this.mWidth <= 0) {
            this.mWidth = width
            this.mHeight = height

            mHourR = mWidth * 0.143f
            mMinuteR = mWidth * 0.35f
            mSecondR = mWidth * 0.35f + spacing //+spacing是为了有些距离（没有其他含义）
        }
    }

    //停止后续绘制，供动态壁纸使用
    fun stopInvalidate() {
        mAnimator.removeAllUpdateListeners()
    }

}



